Telegram Group & Telegram Channel
🐍 Задача с подвохом: Декораторы и изменяемые объекты

Условие:

Что выведет следующий код и почему?

def memoize(fn):
cache = {}
def wrapper(arg):
if arg in cache:
print("Из кэша")
return cache[arg]
else:
result = fn(arg)
cache[arg] = result
return result
return wrapper

@memoize
def add_to_list(val, lst=[]):
lst.append(val)
return lst

res1 = add_to_list(1)
res2 = add_to_list(2)
res3 = add_to_list(1)

print(res1)
print(res2)
print(res3)

Вопрос:
Что именно выведется? В чём здесь двойная ловушка?

🔍 Анализ:

Сначала кажется, что:

1. add_to_list(1) вернёт [1].
2. add_to_list(2) вернёт [2].
3. add_to_list(1) либо вызовет функцию снова, либо вернёт результат из кэша.

Но есть два подвоха:

Подвох №1: изменяемый аргумент по умолчанию

Аргумент lst=[] создаётся один раз при определении функции. Все вызовы без передачи списка будут использовать один и тот же список.

Подвох №2: кэширование по ключу

Декоратор memoize сохраняет результат в кэше по ключу arg. Но функция возвращает список, который изменяется при каждом вызове. Даже если результат берётся из кэша, вы получите ссылку на тот же список, который менялся между вызовами!

🧮 Что реально произойдёт:

- `res1 = add_to_list(1)` → функция вызвана, список становится `[1]`.
- `res2 = add_to_list(2)` → функция вызвана снова с другим аргументом, список теперь `[1, 2]`.
- `res3 = add_to_list(1)` → аргумент `1` есть в кэше, сработает ветка `print("Из кэша")`, и вернётся ссылка на тот же изменённый список.

🔢 Итог:

```
[1, 2]
[1, 2]
Из кэша
[1, 2]
```

Все переменные указывают на один и тот же изменённый список.

💥 Почему это важно:

1️⃣ Изменяемые аргументы по умолчанию сохраняются между вызовами функции.
2️⃣ Кэширование изменяемых объектов может привести к неожиданным результатам: возвращается не неизменяемый результат, а ссылка на объект, который может изменяться позже.

🛡️ Как исправить:

1️⃣ Использовать `lst=None` и создавать новый список внутри функции:
```python
def add_to_list(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
```

2️⃣ Если кэшировать изменяемые объекты, лучше возвращать их копии:
```python
import copy
cache[arg] = copy.deepcopy(result)
```

Итог:

Декораторы вместе с изменяемыми аргументами — это ловушка даже для опытных программистов. Особенно, если изменяемые объекты кэшируются и потом меняются за кулисами.

@Python_Community_ru



tg-me.com/Python_Community_ru/2597
Create:
Last Update:

🐍 Задача с подвохом: Декораторы и изменяемые объекты

Условие:

Что выведет следующий код и почему?

def memoize(fn):
cache = {}
def wrapper(arg):
if arg in cache:
print("Из кэша")
return cache[arg]
else:
result = fn(arg)
cache[arg] = result
return result
return wrapper

@memoize
def add_to_list(val, lst=[]):
lst.append(val)
return lst

res1 = add_to_list(1)
res2 = add_to_list(2)
res3 = add_to_list(1)

print(res1)
print(res2)
print(res3)

Вопрос:
Что именно выведется? В чём здесь двойная ловушка?

🔍 Анализ:

Сначала кажется, что:

1. add_to_list(1) вернёт [1].
2. add_to_list(2) вернёт [2].
3. add_to_list(1) либо вызовет функцию снова, либо вернёт результат из кэша.

Но есть два подвоха:

Подвох №1: изменяемый аргумент по умолчанию

Аргумент lst=[] создаётся один раз при определении функции. Все вызовы без передачи списка будут использовать один и тот же список.

Подвох №2: кэширование по ключу

Декоратор memoize сохраняет результат в кэше по ключу arg. Но функция возвращает список, который изменяется при каждом вызове. Даже если результат берётся из кэша, вы получите ссылку на тот же список, который менялся между вызовами!

🧮 Что реально произойдёт:

- `res1 = add_to_list(1)` → функция вызвана, список становится `[1]`.
- `res2 = add_to_list(2)` → функция вызвана снова с другим аргументом, список теперь `[1, 2]`.
- `res3 = add_to_list(1)` → аргумент `1` есть в кэше, сработает ветка `print("Из кэша")`, и вернётся ссылка на тот же изменённый список.

🔢 Итог:

```
[1, 2]
[1, 2]
Из кэша
[1, 2]
```

Все переменные указывают на один и тот же изменённый список.

💥 Почему это важно:

1️⃣ Изменяемые аргументы по умолчанию сохраняются между вызовами функции.
2️⃣ Кэширование изменяемых объектов может привести к неожиданным результатам: возвращается не неизменяемый результат, а ссылка на объект, который может изменяться позже.

🛡️ Как исправить:

1️⃣ Использовать `lst=None` и создавать новый список внутри функции:
```python
def add_to_list(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
```

2️⃣ Если кэшировать изменяемые объекты, лучше возвращать их копии:
```python
import copy
cache[arg] = copy.deepcopy(result)
```

Итог:

Декораторы вместе с изменяемыми аргументами — это ловушка даже для опытных программистов. Особенно, если изменяемые объекты кэшируются и потом меняются за кулисами.

@Python_Community_ru

BY Python Community


Warning: Undefined variable $i in /var/www/tg-me/post.php on line 283

Share with your friend now:
tg-me.com/Python_Community_ru/2597

View MORE
Open in Telegram


Python Community Telegram | DID YOU KNOW?

Date: |

A project of our size needs at least a few hundred million dollars per year to keep going,” Mr. Durov wrote in his public channel on Telegram late last year. “While doing that, we will remain independent and stay true to our values, redefining how a tech company should operate.

Should You Buy Bitcoin?

In general, many financial experts support their clients’ desire to buy cryptocurrency, but they don’t recommend it unless clients express interest. “The biggest concern for us is if someone wants to invest in crypto and the investment they choose doesn’t do well, and then all of a sudden they can’t send their kids to college,” says Ian Harvey, a certified financial planner (CFP) in New York City. “Then it wasn’t worth the risk.” The speculative nature of cryptocurrency leads some planners to recommend it for clients’ “side” investments. “Some call it a Vegas account,” says Scott Hammel, a CFP in Dallas. “Let’s keep this away from our real long-term perspective, make sure it doesn’t become too large a portion of your portfolio.” In a very real sense, Bitcoin is like a single stock, and advisors wouldn’t recommend putting a sizable part of your portfolio into any one company. At most, planners suggest putting no more than 1% to 10% into Bitcoin if you’re passionate about it. “If it was one stock, you would never allocate any significant portion of your portfolio to it,” Hammel says.

Python Community from ye


Telegram Python Community
FROM USA